package org.smmerframework.component.swagger.config;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.Collections;
import java.util.Map;

/**
 * @author liaorongbiao
 * @version 1.0
 * @date 2019/5/17 10:31 AM
 **/
@EnableSwagger2
@Configuration
@ConditionalOnProperty(name = "spring.swagger2.enable", havingValue = "true")
public class Swagger2Configuration implements BeanFactoryAware {
    private BeanFactory beanFactory;

    private static final ConfigurationPropertyName CLIENT_LOG = ConfigurationPropertyName.of("swaggers");

    private static final Bindable<Map<String, Swagger2Properties>> STRING_CLIENT_LOG_PROPERTY_MAP = Bindable.mapOf(String.class, Swagger2Properties.class);


    @Bean
    @ConditionalOnMissingBean(Docket.class)
    @ConditionalOnProperty(prefix = "spring.swagger2", name = "title")
    public Docket createRestApi(Swagger2Properties swagger2Properties) {
        final ApiSelectorBuilder select = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo(swagger2Properties))
                .select();

        if (StringUtils.isEmpty(swagger2Properties.getBasePackage()) && AutoConfigurationPackages.has(this.beanFactory)) {
            select.apis(RequestHandlerSelectors.basePackage(StringUtils.collectionToCommaDelimitedString(AutoConfigurationPackages.get(this.beanFactory))));
        } else {
            select.apis(RequestHandlerSelectors.basePackage(swagger2Properties.getBasePackage()));
        }

        select.apis(RequestHandlerSelectors.withClassAnnotation(Api.class));
        select.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class));

        return select.paths(PathSelectors.any()).build();
    }

    private static ApiInfo apiInfo(Swagger2Properties swagger2Properties) {
        return new ApiInfoBuilder()
                .title(swagger2Properties.getTitle())
                .description(swagger2Properties.getDescription())
                .termsOfServiceUrl(swagger2Properties.getServiceUrl())
                .version(swagger2Properties.getVersion())
                .build();
    }


    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public static BeanDefinitionRegistryPostProcessor beanDefinitionRegistryPostProcessor(Environment environment) {
        return new BeanDefinitionRegistryPostProcessor() {
            @Override
            public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
                Binder binder = Binder.get(environment);
                Map<String, Swagger2Properties> clientLogPropertys = binder.bind(CLIENT_LOG, STRING_CLIENT_LOG_PROPERTY_MAP).orElseGet(Collections::emptyMap);

                if (MapUtils.isNotEmpty(clientLogPropertys)) {
                    clientLogPropertys.forEach((name, swagger2Properties1) -> {
                        Assert.hasText(swagger2Properties1.getBasePackage(), String.format("swagger config name is %s basePackage is null", name));

                        BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Docket.class, () -> new Docket(DocumentationType.SWAGGER_2)
                                .groupName(swagger2Properties1.getTitle())
                                .apiInfo(apiInfo(swagger2Properties1))
                                .select()
                                .apis(RequestHandlerSelectors.basePackage(swagger2Properties1.getBasePackage()))
                                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                                .paths(PathSelectors.any())
                                .build());
                        definitionBuilder.setLazyInit(false);
                        registry.registerBeanDefinition(name + "Docket", definitionBuilder.getBeanDefinition());
                    });
                }
            }

            @Override
            public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

            }
        };
    }

    @Bean
    @ConfigurationProperties("spring.swagger2")
    public Swagger2Properties swagger2Properties() {
        return new Swagger2Properties();
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}
